/************************************
 * Package: RGcpp					*
 * Authors: Rui Eduardo Gouveia Gil	*
 * Date: 2010						*
 ************************************/
#include "rgcpp/net/sockets/TCPServerSocket.h"

namespace rgcpp
{

TCPServerSocket::TCPServerSocket() : releaseForced(false), depth(DEFAULT_TCP_SERVER_SOCKET_DEPTH), ipAddress((rand()%(65535-1025))+1025)
{
	struct sockaddr_in addr;

	if((socketAccept = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
	{
		Throw(SocketException(std::string("Error creating the socket - ")+strerror(errno)));
	}

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(ipAddress.getPort());
	addr.sin_addr.s_addr = INADDR_ANY;

	//Open the socket
	if(::bind(socketAccept, (struct sockaddr *)&addr, sizeof(addr)) == -1)
	{
		Throw(SocketException(std::string("Error opening the socket - ")+strerror(errno)));
	}

	// Set a limit on connection queue.
	if(::listen(socketAccept, this->depth) != 0)
	{
#ifdef _WIN32
		shutdown(socketAccept,SD_BOTH);
		closesocket(socketAccept);
#else
		shutdown(socketAccept,SHUT_RDWR);
		close(socketAccept);
#endif
		Throw(SocketException(std::string("Unable to set limit on connection queue - ")+strerror(errno)));
	}

	timeout.tv_usec = NO_TIMEOUT;
	timeout.tv_sec = NO_TIMEOUT;
}

TCPServerSocket::TCPServerSocket(const unsigned short port) : releaseForced(false), depth(DEFAULT_TCP_SERVER_SOCKET_DEPTH), ipAddress(port)
{
	struct sockaddr_in addr;

	if((socketAccept = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
	{
		Throw(SocketException(std::string("Error opening the socket - ")+strerror(errno)));
	}

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(ipAddress.getPort());
	addr.sin_addr.s_addr = INADDR_ANY;

	//Open the socket
	if(::bind(socketAccept, (struct sockaddr *)&addr, sizeof(addr)) == -1)
	{
#ifdef _WIN32
		shutdown(socketAccept,SD_BOTH);
		closesocket(socketAccept);
#else
		shutdown(socketAccept,SHUT_RDWR);
		close(socketAccept);
#endif
		Throw(SocketException(std::string("Error opening the socket - ")+strerror(errno)));
	}

	// Set a limit on connection queue.
	if(::listen(socketAccept, this->depth) != 0)
	{
#ifdef _WIN32
		shutdown(socketAccept,SD_BOTH);
		closesocket(socketAccept);
#else
		shutdown(socketAccept,SHUT_RDWR);
		close(socketAccept);
#endif
		Throw(SocketException(std::string("Unable to set limit on connection queue - ")+strerror(errno)));
	}

	timeout.tv_usec = NO_TIMEOUT;
	timeout.tv_sec = NO_TIMEOUT;
}

TCPServerSocket::TCPServerSocket(const IPAddress& ipAddr) : releaseForced(false), depth(DEFAULT_TCP_SERVER_SOCKET_DEPTH), ipAddress(ipAddr)
{
	struct sockaddr_in addr;

	if((socketAccept = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
	{
		Throw(SocketException(std::string("Error creating the socket")+strerror(errno)));
	}

	memset(&addr, 0, sizeof(addr));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(ipAddress.getPort());
	addr.sin_addr.s_addr = htonl(ipAddress.getIP());

	//Open the socket
	if(::bind(socketAccept, (struct sockaddr *)&addr, sizeof(addr)) == -1)
	{
#ifdef _WIN32
		shutdown(socketAccept,SD_BOTH);
		closesocket(socketAccept);
#else
		shutdown(socketAccept,SHUT_RDWR);
		close(socketAccept);
#endif
		Throw(SocketException(std::string("Error opening the socket - ")+strerror(errno)));
	}

	// Set a limit on connection queue.
	if(::listen(socketAccept, this->depth) != 0)
	{
#ifdef _WIN32
		shutdown(socketAccept,SD_BOTH);
		closesocket(socketAccept);
#else
		shutdown(socketAccept,SHUT_RDWR);
		close(socketAccept);
#endif
		Throw(SocketException(std::string("Unable to set limit on connection queue - ")+strerror(errno)));
	}

	timeout.tv_usec = NO_TIMEOUT;
	timeout.tv_sec = NO_TIMEOUT;
}

TCPServerSocket::~TCPServerSocket()
{
	boost::mutex::scoped_lock lock(mut);

#ifdef _WIN32
		shutdown(socketAccept,SD_BOTH);
		closesocket(socketAccept);
#else
		shutdown(socketAccept,SHUT_RDWR);
		close(socketAccept);
#endif
}

TCPSocket* TCPServerSocket::accept()
{
	boost::mutex::scoped_lock lock(mut);

	fd_set fdList;

	FD_ZERO(&fdList);
	FD_SET(socketAccept, &fdList);

	int value;
	struct timeval timeout = this->timeout;
	do
	{
		if(!timeout.tv_usec && !timeout.tv_sec)
			value = select(socketAccept+1,&fdList,NULL,NULL,NULL);
		else
			value = select(socketAccept+1,&fdList,NULL,NULL,&timeout);
	}while(value == -1&& errno == EINTR);

	if(value == 0)
	{
		Throw(SocketException(std::string("Error server socket - Timeout exception")));
	}
	else if(value == -1)
	{
		Throw(SocketException(std::string("Error server socket - ")+strerror(errno)));
	}

	if(this->releaseForced)
		return NULL;

	// Open up new connection
	struct sockaddr_in addr;
	int len = sizeof(addr);

#ifdef _WIN32
	int client = ::accept(socketAccept,(struct sockaddr *)&addr,&len);
#else
	int client = ::accept(socketAccept,(struct sockaddr *)&addr,(socklen_t *)&len);
#endif

	if(client == -1)
	{
		Throw(SocketException(std::string("Invalid socket file descriptor - ")+strerror(errno)));
	}

	return new TCPSocket(client);
}

void TCPServerSocket::setTimeout(const unsigned long timeout)
{
	this->timeout.tv_usec = (timeout % 1000) * 1000;
	this->timeout.tv_sec = timeout / 1000;
}

unsigned long TCPServerSocket::getTimeout()
{
	unsigned long timeout;

	timeout = this->timeout.tv_sec * 1000;
	timeout += this->timeout.tv_usec / 1000;

	return timeout;
}

void TCPServerSocket::release()
{
	this->releaseForced=true;
#ifdef _WIN32
	shutdown(socketAccept,SD_BOTH);
#else
	shutdown(socketAccept,SHUT_RDWR);
#endif
}

bool TCPServerSocket::check4Accept(const unsigned long timeout)
{
	boost::mutex::scoped_lock lock(mut);

	struct timeval time;
	fd_set fdList;

	time.tv_usec = (timeout % 1000) * 1000;
	time.tv_sec = timeout / 1000;

	FD_ZERO(&fdList);
	FD_SET(socketAccept, &fdList);

	if(select(socketAccept+1,&fdList,NULL,NULL,&time) <= 0)
		return false;
	else
		return true;
}

IPAddress TCPServerSocket::getLocalAddress()
{
	return ipAddress;
}

void TCPServerSocket::setDepth(const unsigned short depth)
{
	this->depth=depth;

	// Set a limit on connection queue.
	if(::listen(socketAccept, this->depth) != 0)
	{
		Throw(SocketException(std::string("Unable to set limit on connection queue")+strerror(errno)));
	}
}

unsigned int TCPServerSocket::getDepth()
{
	return this->depth;
}

}
